package com.cloudfun.campusshare.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.cloudfun.campusshare.common.constant.Code;
import com.cloudfun.campusshare.common.exception.BusinessException;
import com.cloudfun.campusshare.common.model.dto.DeviceMetaModeDTO;
import com.cloudfun.campusshare.common.model.dto.DeviceMetaModeSearchDTO;
import com.cloudfun.campusshare.common.model.dto.DeviceProgramDTO;
import com.cloudfun.campusshare.common.model.vo.DryerOperateVO;
import com.cloudfun.campusshare.common.model.vo.PulseWasherOperateVO;
import com.cloudfun.campusshare.common.model.vo.RollerWasherOpVO;
import com.cloudfun.campusshare.common.model.vo.ShoeWasherOperateVO;
import com.cloudfun.campusshare.entity.DeviceMetaModeEntity;
import com.cloudfun.campusshare.entity.DeviceModeProgramRel;
import com.cloudfun.campusshare.entity.DeviceProgramEntity;
import com.cloudfun.campusshare.repo.DeviceMetaModeRepo;
import com.cloudfun.campusshare.repo.DeviceModeProgramRelRepo;
import com.cloudfun.campusshare.repo.DeviceProgramRepo;
import com.cloudfun.campusshare.service.DeviceMetaModeService;
import com.cloudfun.campusshare.util.WasherControlUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.Predicate;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Created by Huqin on 2020/4/20
 */
@Slf4j
@Service
// TODO 加缓存
public class DeviceMetaModeServiceImpl implements DeviceMetaModeService {

    @Autowired
    private DeviceMetaModeRepo deviceMetaModeRepo;

    @Autowired
    private DeviceProgramRepo deviceProgramRepo;

    @Autowired
    private DeviceModeProgramRelRepo deviceModeProgramRelRepo;

    @Override
    public Map<String, DeviceMetaModeDTO> getDeviceMetaModeMap() {
        List<DeviceMetaModeEntity> metaModeEntities = deviceMetaModeRepo.findAll();
        List<DeviceProgramEntity> programEntities = deviceProgramRepo.findAll();
        List<DeviceModeProgramRel> modeProgramRelations = deviceModeProgramRelRepo.findAll();
        return this.formMap(metaModeEntities, programEntities, modeProgramRelations);
    }

    @Override
    public Map<String, DeviceMetaModeDTO> getDeviceMetaModeMap(String deviceType) {
        if (StringUtils.isBlank(deviceType)) {
            return new HashMap<>();
        }
        List<DeviceMetaModeEntity> metaModeEntities = deviceMetaModeRepo.findAllByDeviceType(deviceType);
        List<DeviceProgramEntity> programEntities = deviceProgramRepo.findAll();
        List<DeviceModeProgramRel> modeProgramRelations = deviceModeProgramRelRepo.findAll();
        return this.formMap(metaModeEntities, programEntities, modeProgramRelations);
    }

    @Override
    public List<DeviceMetaModeDTO> getDeviceMetaModeMap(DeviceMetaModeSearchDTO searchDTO) {
        Specification<DeviceMetaModeEntity> spec = (root, query, criteriaBuilder) -> {
            List<Predicate> list = new ArrayList<>();
            if (StringUtils.isNotBlank(searchDTO.getBrandId())) {
                list.add(criteriaBuilder.equal(root.get("brandId"), searchDTO.getBrandId()));
            }
            if (null != searchDTO.getProductType()) {
                list.add(criteriaBuilder.equal(root.get("productType"), searchDTO.getProductType()));
            }
            if (StringUtils.isNotBlank(searchDTO.getDeviceType())) {
                list.add(criteriaBuilder.equal(root.get("deviceType"), searchDTO.getDeviceType()));
            }
            list.add(criteriaBuilder.equal(root.get("status"), true));
            Predicate[] predicates = new Predicate[list.size()];
            return criteriaBuilder.and(list.toArray(predicates));
        };

        List<DeviceMetaModeEntity> metaModeEntities = deviceMetaModeRepo.findAll(spec);
        return metaModeEntities.stream()
                .map(metaModeEntity -> {
                    DeviceMetaModeDTO deviceMetaModeDTO = new DeviceMetaModeDTO();
                    BeanUtil.copyProperties(metaModeEntity, deviceMetaModeDTO);
                    return deviceMetaModeDTO;
                }).collect(Collectors.toList());
    }

    private Map<String, DeviceMetaModeDTO> formMap(List<DeviceMetaModeEntity> metaModeEntities, List<DeviceProgramEntity> programEntities,
                                                   List<DeviceModeProgramRel> modeProgramRelations) {
        Map<String, List<DeviceModeProgramRel>> metaModeIdProgramsMap = modeProgramRelations.stream().collect(Collectors.groupingBy(DeviceModeProgramRel::getMetaModeId));
        Map<String, DeviceProgramEntity> idProgramMap = programEntities.stream().collect(Collectors.toMap(DeviceProgramEntity::getId, Function.identity()));

        List<DeviceMetaModeDTO> metaModeDTOList = metaModeEntities.stream()
                .map(metaModeEntity -> {
                    DeviceMetaModeDTO deviceMetaModeDTO = new DeviceMetaModeDTO();
                    BeanUtil.copyProperties(metaModeEntity, deviceMetaModeDTO);
                    String metaModeId = metaModeEntity.getId();

                    List<DeviceModeProgramRel> relations = metaModeIdProgramsMap.getOrDefault(metaModeId, new ArrayList<>());
                    List<DeviceProgramDTO> programDTOList = relations.stream()
                            .map(relation -> {
                                String programId = relation.getProgramId();
                                DeviceProgramDTO deviceProgramDTO = new DeviceProgramDTO();
                                DeviceProgramEntity deviceProgramEntity = idProgramMap.get(programId);
                                BeanUtil.copyProperties(deviceProgramEntity, deviceProgramDTO);
                                deviceProgramDTO.setMainProgram(relation.isMainProgram());
                                return deviceProgramDTO;
                            }).collect(Collectors.toList());
                    deviceMetaModeDTO.setPrograms(programDTOList);
                    return deviceMetaModeDTO;
                }).collect(Collectors.toList());
        return metaModeDTOList.stream().collect(Collectors.toMap(DeviceMetaModeDTO::getId, Function.identity()));
    }

    @Override
    public RollerWasherOpVO metaModeToDeviceControlVO(String id) {
        DeviceMetaModeDTO deviceMetaModeDTO = this.getDeviceMetaModeMap().get(id);
        return this.metaModeToDeviceControlVO(deviceMetaModeDTO);
    }

    @Override
    public DryerOperateVO metaModeToDryerOperateVO(String id) {
        DeviceMetaModeDTO deviceMetaModeDTO = this.getDeviceMetaModeMap().get(id);
        return this.metaModeToDryerOperateVO(deviceMetaModeDTO);
    }

    @Override
    public PulseWasherOperateVO metaModeToPulseWasherOperateVO(String id) {
        DeviceMetaModeDTO deviceMetaModeDTO = this.getDeviceMetaModeMap().get(id);
        return this.metaModeToPulseWasherOperateVO(deviceMetaModeDTO);
    }

    @Override
    public ShoeWasherOperateVO metaModeToShoeWasherOperateVO(String id) {
        DeviceMetaModeDTO deviceMetaModeDTO = this.getDeviceMetaModeMap().get(id);
        return this.metaModeToShoeWasherOperateVO(deviceMetaModeDTO);
    }

    @Override
    public RollerWasherOpVO metaModeToDeviceControlVO(DeviceMetaModeDTO deviceMetaModeDTO) {
        List<DeviceProgramDTO> programs = deviceMetaModeDTO.getPrograms();
        DeviceProgramDTO mainProgram = programs.stream().filter(DeviceProgramDTO::getMainProgram).findAny()
                .orElseThrow(() -> new BusinessException(Code.NODATA, deviceMetaModeDTO.getId() + "的主程序不存在"));
        Map<String, String> additionPrograms = programs.stream().collect(Collectors.toMap(DeviceProgramDTO::getName, DeviceProgramDTO::getValue));
        boolean heat40 = StringUtils.equals(additionPrograms.get("heat"), "heat40");
        boolean heat60 = StringUtils.equals(additionPrograms.get("heat"), "heat60");
        boolean speedUpStatus = Boolean.parseBoolean(additionPrograms.get("speedUpStatus"));
        boolean quietRinseStatus = Boolean.parseBoolean(additionPrograms.get("quietRinseStatus"));
        boolean addLaundryDetergent = Boolean.parseBoolean(additionPrograms.get("addLaundryDetergent"));
        RollerWasherOpVO controlVO = new RollerWasherOpVO();
        controlVO.setWashType(WasherControlUtil.getWasherWashType(mainProgram.getValue(), heat40, heat60, speedUpStatus, quietRinseStatus));
        controlVO.setAddLaundryDetergent(addLaundryDetergent);
        return controlVO;
    }

    @Override
    public DryerOperateVO metaModeToDryerOperateVO(DeviceMetaModeDTO deviceMetaModeDTO) {
        List<DeviceProgramDTO> programs = deviceMetaModeDTO.getPrograms();
        DeviceProgramDTO mainProgram = programs.stream().filter(DeviceProgramDTO::getMainProgram).findAny()
                .orElseThrow(() -> new BusinessException(Code.NODATA, deviceMetaModeDTO.getId() + "的主程序不存在"));
        DryerOperateVO operateVO = new DryerOperateVO();
        operateVO.setTempLevel(Integer.valueOf(mainProgram.getValue()));
        return operateVO;
    }

    @Override
    public PulseWasherOperateVO metaModeToPulseWasherOperateVO(DeviceMetaModeDTO deviceMetaModeDTO) {
        List<DeviceProgramDTO> programs = deviceMetaModeDTO.getPrograms();
        DeviceProgramDTO mainProgram = programs.stream().filter(DeviceProgramDTO::getMainProgram).findAny()
                .orElseThrow(() -> new BusinessException(Code.NODATA, deviceMetaModeDTO.getId() + "的主程序不存在"));
        Map<String, String> additionPrograms = programs.stream().collect(Collectors.toMap(DeviceProgramDTO::getName, DeviceProgramDTO::getValue));
        boolean addLaundryDetergent = Boolean.parseBoolean(additionPrograms.get("addLaundryDetergent"));
        boolean disinfection = Boolean.parseBoolean(additionPrograms.get("disinfection"));
        PulseWasherOperateVO operateVO = new PulseWasherOperateVO();
        operateVO.setWashType(Integer.valueOf(mainProgram.getValue()));
        operateVO.setDisinfection(disinfection);
        operateVO.setAddLaundryDetergent(addLaundryDetergent);
        return operateVO;
    }

    @Override
    public ShoeWasherOperateVO metaModeToShoeWasherOperateVO(DeviceMetaModeDTO deviceMetaModeDTO) {
        List<DeviceProgramDTO> programs = deviceMetaModeDTO.getPrograms();
        DeviceProgramDTO mainProgram = programs.stream().filter(DeviceProgramDTO::getMainProgram).findAny()
                .orElseThrow(() -> new BusinessException(Code.NODATA, deviceMetaModeDTO.getId() + "的主程序不存在"));
        Map<String, String> additionPrograms = programs.stream().collect(Collectors.toMap(DeviceProgramDTO::getName, DeviceProgramDTO::getValue));
        boolean disinfection = Boolean.parseBoolean(additionPrograms.get("disinfection"));
        boolean addLaundryDetergent = Boolean.parseBoolean(additionPrograms.get("addLaundryDetergent"));
        ShoeWasherOperateVO operateVO = new ShoeWasherOperateVO();
        operateVO.setWashType(Integer.valueOf(mainProgram.getValue()));
        operateVO.setDisinfection(disinfection);
        operateVO.setAddLaundryDetergent(addLaundryDetergent);
        return operateVO;
    }
}
